﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;

namespace Microsoft.Interop
{
    /// <summary>
    /// Default resolver for marshalling generators.
    /// </summary>
    public static class DefaultMarshallingGeneratorResolver
    {
        /// <summary>
        /// Creates a marshalling generator resolver with support for all marshalling information generated by <see cref="DefaultMarshallingInfoParser"/>.
        /// </summary>
        /// <param name="env">Flags about the compilation environment for emitted code.</param>
        /// <param name="direction">The direction of marshalling.</param>
        /// <param name="stringMarshallingAttribute">The name of the attribute type where the <see cref="T:System.Runtime.InteropServices.Marshalling.StringMarshalling"/> marshalling data is specified by the user.</param>
        /// <param name="additionalResolvers">Additional resolvers for resolving generators for any custom marshalling information.</param>
        /// <returns>A marshalling generator with support for marshalling types supported by <see cref="DefaultMarshallingInfoParser"/> as well as any types supported by the additional resolvers.</returns>
        public static IMarshallingGeneratorResolver Create(
            EnvironmentFlags env,
            MarshalDirection direction,
            string stringMarshallingAttribute,
            IEnumerable<IMarshallingGeneratorResolver> additionalResolvers)
        {
            IMarshallingGeneratorResolver fallbackResolver = new NotSupportedResolver();
            List<IMarshallingGeneratorResolver> coreResolvers = [
                .. additionalResolvers,
                new MarshalAsMarshallingGeneratorResolver(new InteropGenerationOptions(UseMarshalType: true)),
                new NoMarshallingInfoErrorResolver(TypeNames.GeneratedComInterfaceAttribute_ShortName),
                ];

            // Since the char type in an array will not be part of the P/Invoke signature, we can
            // use the regular blittable marshaller in all cases.
            var charElementMarshaller = new CharMarshallingGeneratorResolver(useBlittableMarshallerForUtf16: true, stringMarshallingAttribute);

            IMarshallingGeneratorResolver elementFactory = new AttributedMarshallingModelGeneratorResolver(
                new CompositeMarshallingGeneratorResolver([
                    .. coreResolvers,
                    // Since the char type in an array will not be part of the P/Invoke signature, we can
                    // use the regular blittable marshaller in all cases.
                    charElementMarshaller,
                    fallbackResolver,
                ]),
                new AttributedMarshallingModelOptions(
                    env.HasFlag(EnvironmentFlags.DisableRuntimeMarshalling),
                    MarshalMode.ElementIn,
                    MarshalMode.ElementRef,
                    MarshalMode.ElementOut,
                    ResolveElementsFromSelf: true));

            IMarshallingGeneratorResolver generatorResolver = new ByValueContentsMarshalKindValidator(
                new CompositeMarshallingGeneratorResolver(
                    [
                        .. coreResolvers,
                        new AttributedMarshallingModelGeneratorResolver(
                            new CompositeMarshallingGeneratorResolver(
                               [elementFactory, .. coreResolvers, charElementMarshaller, fallbackResolver]),
                            new AttributedMarshallingModelOptions(
                                env.HasFlag(EnvironmentFlags.DisableRuntimeMarshalling),
                                direction == MarshalDirection.ManagedToUnmanaged
                                    ? MarshalMode.ManagedToUnmanagedIn
                                    : MarshalMode.UnmanagedToManagedOut,
                                direction == MarshalDirection.ManagedToUnmanaged
                                    ? MarshalMode.ManagedToUnmanagedRef
                                    : MarshalMode.UnmanagedToManagedRef,
                                direction == MarshalDirection.ManagedToUnmanaged
                                    ? MarshalMode.ManagedToUnmanagedOut
                                    : MarshalMode.UnmanagedToManagedIn,
                                ResolveElementsFromSelf: false)),
                        // Since the char type can go into the P/Invoke signature here, we can only use it when
                        // runtime marshalling is disabled.
                        new CharMarshallingGeneratorResolver(useBlittableMarshallerForUtf16: env.HasFlag(EnvironmentFlags.DisableRuntimeMarshalling), stringMarshallingAttribute),
                        fallbackResolver
                    ]));
            generatorResolver = new BreakingChangeDetector(generatorResolver);

            return generatorResolver;
        }
    }
}
